package com.lsh.consumer;

import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * @author ：LiuShihao
 * @date ：Created in 2021/3/19 3:13 下午
 * @desc ：
 */
@Slf4j
@Component
public class RabbitMQConsumer {

    @Autowired
    RedisTemplate redisTemplate;



    /**
     * 指定序列化方式
     */
    @PostConstruct
    public void init() {
        RedisSerializer redisSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(redisSerializer);
        redisTemplate.setValueSerializer(redisSerializer);
        redisTemplate.setHashKeySerializer(redisSerializer);
        redisTemplate.setHashValueSerializer(redisSerializer);
    }


    /**
     * 如果开启了ack 需要手动确认，否则消息会一直存在，导致重复消费。
     * @param msg
     * @throws Exception
     */
    @RabbitHandler
    @RabbitListener(queues = "queue")
    public void consumerMsg(String msg, Channel channel, Message message) throws Exception{
        try{
            System.out.println("==============================");
            System.out.println("provider端：监听到的消息为："+msg);
            //TODO 具体业务
            System.out.println(1/0);
            redisTemplate.opsForValue().decrement("AppleStock",1L);
            String appleStock = (String) redisTemplate.opsForValue().get("AppleStock");
            System.out.println("苹果库存扣减成功，库存还剩 "+appleStock+" 个");

//            System.out.println(1/0);
            //basicAck：表示成功确认，使用此回执方法后，消息会被rabbitmq broker 删除。
            //deliveryTag：表示消息投递序号，每次消费消息或者消息重新投递后，deliveryTag都会增加。手动消息确认模式下，我们可以对指定deliveryTag的消息进行ack、nack、reject等操作。
            //multiple：是否批量确认，值为 true 则会一次性 ack所有小于当前消息 deliveryTag 的消息。
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        }catch (Exception e){
            //消息是否被重发getRedelivered(): true 消息已经被重复投递过 ； false  消息没有被重复投递过
            if (message.getMessageProperties().getRedelivered()) {
                /**
                 * 消息已经被重复投递过
                 * 消费消息时仍然发生异常
                 * 拒绝处理消息
                 */
                log.error("消息已重复处理失败,拒绝再次接收...");
                //basicReject：拒绝消息，与basicNack区别在于不能进行批量操作，其他用法很相似。
                //deliveryTag：表示消息投递序号。
                //requeue：值为 true 消息将重新入队列。

                //拒绝消息
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), false); // 拒绝消息
                //再次投递
//                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);

            } else {

                /**
                 * 消息消费异常
                 * 且消息没有重复投递过
                 * 将消息重新返回队列再次消息
                 */
                log.error("消息即将再次返回队列处理...");
                //basicNack ：表示失败确认，一般在消费消息业务异常时用到此方法，可以将消息重新投递入队列。
                //deliveryTag：表示消息投递序号。
                //multiple：是否批量确认。
                //requeue：值为 true 消息将重新入队列。
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
            }

        }
    }
}
